using System;
using System.IO;

namespace Game.Lib
{
    public class Color : Vector
    {
        #region Constants
        
        public readonly static Color ColorBlack        = new Color();
        public readonly static Color ColorRed          = new Color(1f, 0f, 0f);
        public readonly static Color ColorGreen        = new Color(0f, 1f, 0f);
        public readonly static Color ColorBlue         = new Color(0f, 0f, 1f);
        public readonly static Color ColorWhite        = new Color(1f, 1f, 1f);
        
        public readonly static byte FLAG_RED      = 0x1;
        public readonly static byte FLAG_GREEN    = 0x2;
        public readonly static byte FLAG_BLUE     = 0x4;
        public readonly static byte FLAG_FILTER   = 0x8;
        public readonly static byte FLAG_TRANSMIT = 0x10;
        public readonly static byte FLAG_EXACT    = 0x20;

        public readonly static float INVERSE_MAX_VEC = 1f / Vector3.AllOne.Magnitude();
        
        #endregion
        
        #region Fields
        
        public float Red;
        public float Green;
        public float Blue;
        public float Filter;
        public float Transmit;
        public bool Exact = true;
        
        #endregion
        
        #region Constrctors
        
        public Color()
        {
            Red = Green = Blue = Filter = Transmit = 0f;
        }
        
        public Color(Vector3 a)
        {
            Red = a.X;
            Green = a.Y;
            Blue = a.Z;
            Filter = Transmit = 0f;
        }
        
        public Color(Vector3 rgb, float f, float t)
        {
            Red = rgb.X;
            Green = rgb.Y;
            Blue = rgb.Z;
            Filter = f;
            Transmit = t;
        }
        
        public Color(float r, float g, float b)
        {
            Red = r;
            Green = g;
            Blue = b;
            Filter = Transmit = 0f;
        }
        
        public Color(float r, float g, float b, float f, float t)
        {
            Red = r;
            Green = g;
            Blue = b;
            Filter = f;
            Transmit = t;
        }
        
        #endregion
        
        #region Private Methods
        
        private int GetByteLen()
        {
            int cnt = 0;
            if(Red != 0f) ++cnt;
            if(Green != 0f) ++cnt;
            if(Blue != 0f) ++cnt;
            if(Filter != 0f) ++cnt;
            if(Transmit != 0f) ++cnt;
            if(Exact) return cnt * 4 + 1;
            else return cnt + 1;
        }
        
        private byte GetByteFlags()
        {
            byte ret = 0;
            if(Red != 0f) ret |= FLAG_RED;
            if(Green != 0f) ret |= FLAG_GREEN;
            if(Blue != 0f) ret |= FLAG_BLUE;
            if(Filter != 0f) ret |= FLAG_FILTER;
            if(Transmit != 0f) ret |= FLAG_TRANSMIT;
            if(Exact) ret |= FLAG_EXACT;
            return ret;
        }
        
        #endregion
        
        #region Public Methods
        
        public void Normalize()
        {
            float mag = Magnitude();
            if(mag < .0000001)
            {
                Red = Blue = Green = Filter = Transmit = 0f;
            }
            else
            {
                Red = Utils.Clamp(Red, 0f, 1f);
                Green = Utils.Clamp(Green, 0f, 1f);
                Blue = Utils.Clamp(Blue, 0f, 1f);
                Filter = Utils.Clamp(Filter, 0f, 1f);
                Transmit = Utils.Clamp(Transmit, 0f, 1f);
            }
        }

        public float Magnitude()
        {
            return (float)Math.Sqrt(Red * Red + Green * Green + Blue * Blue + Filter * Filter + Transmit * Transmit);
        }
        
        public float MagnitudeSquared()
        {
            return Red * Red + Green * Green + Blue * Blue + Filter * Filter + Transmit * Transmit;
        }

        public Color Inverse()
        {
            return new Color(1f - Red, 1f - Green, 1f - Blue, 1f - Filter, 1f - Transmit);
        }
        
        public override int GetHashCode()
        {
            return Red.GetHashCode() ^ Green.GetHashCode() ^ Blue.GetHashCode() ^ Filter.GetHashCode() ^ Transmit.GetHashCode();
        }
        
        public override string ToString()
        {
            return String.Format(Utils.EnUsCulture, "<{0}, {1}, {2}, {3}, {4}>", Red, Green, Blue, Filter, Transmit);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Color)) return false;
            Color b = (Color)obj;
            return  this.Red == b.Red &&
                    this.Green == b.Green &&
                    this.Blue == b.Blue &&
                    this.Filter == b.Filter &&
                    this.Transmit == b.Transmit;
        }
        
        #endregion
        
        #region Static Methods

        public static Color FromHSV(float H, float S, float V)
        {
            if (S == 0f)
            {
                return new Color(V, V, V);
            }
            else
            {
                if (H >= 1.0)
                {
                    H = 0f;
                }
                else
                {
                    H *= 6f;
                }
                int I = (int)H;
                float F = H - I;

                float M = V * (1f - S);
                float N = V * (1f - S * F);
                float K = V * (1f - S * (1f - F));

                float R = 0f, G = 0f, B = 0f;

                if (I == 0) { R = V; G = K; B = M; }
                if (I == 1) { R = N; G = V; B = M; }
                if (I == 2) { R = M; G = V; B = K; }
                if (I == 3) { R = M; G = N; B = V; }
                if (I == 4) { R = K; G = M; B = V; }
                if (I == 5) { R = V; G = M; B = N; }

                return new Color(R, G, B);
            }
        }

        
        public static Color Parse(string val)
        {
            string[] split = val.Replace("<", String.Empty).Replace(">", String.Empty).Split(new char[] { ',' });
            if(split.Length == 3)
            {
                return new Color(
                    Single.Parse(split[0].Trim(), Utils.EnUsCulture),
                    Single.Parse(split[1].Trim(), Utils.EnUsCulture),
                    Single.Parse(split[2].Trim(), Utils.EnUsCulture));
            }
            else if(split.Length == 5)
            {
                return new Color(
                    Single.Parse(split[0].Trim(), Utils.EnUsCulture),
                    Single.Parse(split[1].Trim(), Utils.EnUsCulture),
                    Single.Parse(split[2].Trim(), Utils.EnUsCulture),
                    Single.Parse(split[3].Trim(), Utils.EnUsCulture),
                    Single.Parse(split[4].Trim(), Utils.EnUsCulture));
            }
            else throw new ArgumentException("Colors must be of the form \"<R, G, B>\" or \"<R, G, B, F, T>\"");
        }
        
        #endregion
        
        #region Operators
        
        public static Color operator +(Color a, Color b)
        {
            return new Color(a.Red + b.Red, a.Green + b.Green, a.Blue + b.Blue, a.Filter + b.Filter, a.Transmit + b.Transmit);
        }
        
        public static Color operator -(Color a, Color b)
        {
            return new Color(a.Red - b.Red, a.Green - b.Green, a.Blue - b.Blue, a.Filter - b.Filter, a.Transmit - b.Transmit);
        }
        
        /// <summary>
        /// Computes the mix of color a and b
        /// </summary>
        public static Color operator *(Color a, Color b)
        {
            throw new NotImplementedException();
        }

        public static bool operator ==(Color a, Color b)
        {
            return a.Equals(b);
        }

        public static bool operator !=(Color a, Color b)
        {
            return  a.Red != b.Red ||
                    a.Green != b.Green ||
                    a.Blue != b.Blue ||
                    a.Filter != b.Filter ||
                    a.Transmit != b.Transmit;
        }
        
        #endregion
        
        #region Serialization

        public void FromBytes(byte[] data, int pos)
        {
            byte recon = data[0];
            Red = Green = Blue = Filter = Transmit = 0f;
            if((recon & FLAG_EXACT) != 0)
            {
                if((recon & FLAG_RED) != 0)
                {
                    Red = BitConverter.ToSingle(data, pos);
                    pos += 4;
                }
                if((recon & FLAG_GREEN) != 0)
                {
                    Green = BitConverter.ToSingle(data, 0);
                    pos += 4;
                }
                if((recon & FLAG_BLUE) != 0)
                {
                    Blue = BitConverter.ToSingle(data, 0);
                    pos += 4;
                }
                if((recon & FLAG_FILTER) != 0)
                {
                    Filter = BitConverter.ToSingle(data, 0);
                    pos += 4;
                }
                if((recon & FLAG_TRANSMIT) != 0)
                {
                    Transmit = BitConverter.ToSingle(data, 0);
                    pos += 4;
                }
            }
            else
            {
                if((recon & FLAG_RED) != 0) Red = (float)data[pos++] / 255f;
                if((recon & FLAG_GREEN) != 0) Green = (float)data[pos++] / 255f;
                if((recon & FLAG_BLUE) != 0) Blue = (float)data[pos++] / 255f;
                if((recon & FLAG_FILTER) != 0) Filter = (float)data[pos++] / 255f;
                if((recon & FLAG_TRANSMIT) != 0) Transmit = (float)data[pos++] / 255f;
            }
        }
        
        public byte[] ToBytes()
        {
            byte[] dest = new byte[GetByteLen()];
            byte nonZero = dest[0] = GetByteFlags();
	        int pos = 1;
            if(Exact)
            {
	            bool flip = !BitConverter.IsLittleEndian;
	            if((nonZero & FLAG_RED) != 0)
	            {
	                Buffer.BlockCopy(BitConverter.GetBytes(Red), 0, dest, pos, 4);
	                if(flip) Array.Reverse(dest, pos, 4);
	                pos += 4;
	            }
	            if((nonZero & FLAG_GREEN) != 0)
	            {
	                Buffer.BlockCopy(BitConverter.GetBytes(Green), 0, dest, pos, 4);
	                if(flip) Array.Reverse(dest, pos, 4);
	                pos += 4;
	            }
	            if((nonZero & FLAG_BLUE) != 0)
	            {
	                Buffer.BlockCopy(BitConverter.GetBytes(Blue), 0, dest, pos, 4);
	                if(flip) Array.Reverse(dest, pos, 4);
	                pos += 4;
	            }
	            if((nonZero & FLAG_FILTER) != 0)
	            {
	                Buffer.BlockCopy(BitConverter.GetBytes(Filter), 0, dest, pos, 4);
	                if(flip) Array.Reverse(dest, pos, 4);
	                pos += 4;
	            }
	            if((nonZero & FLAG_TRANSMIT) != 0)
	            {
	                Buffer.BlockCopy(BitConverter.GetBytes(Transmit), 0, dest, pos, 4);
	                if(flip) Array.Reverse(dest, pos, 4);
	                pos += 4;
	            }
	            return dest;
            }
            else
            {
	            if((nonZero & FLAG_RED) != 0) dest[pos++] = (byte)(Red * 255f);
	            if((nonZero & FLAG_GREEN) != 0) dest[pos++] = (byte)(Green * 255f);
	            if((nonZero & FLAG_BLUE) != 0) dest[pos++] = (byte)(Blue * 255f);
	            if((nonZero & FLAG_FILTER) != 0) dest[pos++] = (byte)(Filter * 255f);
	            if((nonZero & FLAG_TRANSMIT) != 0) dest[pos++] = (byte)(Transmit * 255f);
	            return dest;
            }
        }
        
        public void Serialize(Stream s)
        {
            if(!s.CanWrite) throw new ArgumentException("Serialization requires a writable stream");
            s.Write(ToBytes(), 0, GetByteLen());
        }
        
        public void Deserialize(Stream s)
        {
            if(!s.CanRead) throw new ArgumentException("Deserialization requires a readable stream");
            int temp = s.ReadByte();
            if(temp == -1) throw new EndOfStreamException("Insufficent data to reconstruct color.");
            byte recon = (byte)temp;
            Red = Green = Blue = Filter = Transmit = 0f;
            if((recon & FLAG_EXACT) != 0)
            {
                byte[] buf = new byte[4];
                if((recon & FLAG_RED) != 0)
                {
                    temp = s.Read(buf, 0, 4);
                    if(temp != 4) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Red = BitConverter.ToSingle(buf, 0);
                }
                if((recon & FLAG_GREEN) != 0)
                {
                    temp = s.Read(buf, 0, 4);
                    if(temp != 4) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Green = BitConverter.ToSingle(buf, 0);
                }
                if((recon & FLAG_BLUE) != 0)
                {
                    temp = s.Read(buf, 0, 4);
                    if(temp != 4) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Blue = BitConverter.ToSingle(buf, 0);
                }
                if((recon & FLAG_FILTER) != 0)
                {
                    temp = s.Read(buf, 0, 4);
                    if(temp != 4) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Filter = BitConverter.ToSingle(buf, 0);
                }
                if((recon & FLAG_TRANSMIT) != 0)
                {
                    temp = s.Read(buf, 0, 4);
                    if(temp != 4) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Transmit = BitConverter.ToSingle(buf, 0);
                }
            }
            else
            {
                if((recon & FLAG_RED) != 0)
                {
                    temp = s.ReadByte();
                    if(temp == -1) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Red = (float)temp / 255f;
                }
                if((recon & FLAG_GREEN) != 0)
                {
                    temp = s.ReadByte();
                    if(temp == -1) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Green = (float)temp / 255f;
                }
                if((recon & FLAG_BLUE) != 0)
                {
                    temp = s.ReadByte();
                    if(temp == -1) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Blue = (float)temp / 255f;
                }
                if((recon & FLAG_FILTER) != 0)
                {
                    temp = s.ReadByte();
                    if(temp == -1) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Filter = (float)temp / 255f;
                }
                if((recon & FLAG_TRANSMIT) != 0)
                {
                    temp = s.ReadByte();
                    if(temp == -1) throw new EndOfStreamException("Insufficent data to reconstruct color.");
                    Transmit = (float)temp / 255f;
                }
            }
        }
        
        #endregion
    }
}
